﻿// -----------------------------------------------------------------------
// <copyright file="GroupedObservableCollection.cs" company="">
//      Copyright 2012 Samuel Englard
//      
//      Microsoft Reciprocal License (Ms-RL)
// 
//      This license governs use of the accompanying software. If you use the software, you accept this license. If you do 
//      not accept the license, do not use the software.
// 
//      1. Definitions
// 
//      The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under 
//      U.S. copyright law.
// 
//      A "contribution" is the original software, or any additions or changes to the software.
// 
//      A "contributor" is any person that distributes its contribution under this license.
// 
//      "Licensed patents" are a contributor's patent claims that read directly on its contribution.
// 
//      2. Grant of Rights
// 
//      (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in 
//      section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its 
//      contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works 
//      that you create.
// 
//      (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 
//      3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, 
//      have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or 
//      derivative works of the contribution in the software.
// 
//      3. Conditions and Limitations
// 
//      (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary 
//      format), you must provide recipients the source code to that file along with a copy of this license, which license 
//      will govern that file. You may license other files that are entirely your own work and do not contain code from the
//      software under any terms you choose.
// 
//      (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
// 
//      (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, 
//      your patent license from such contributor to the software ends automatically.
// 
//      (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution 
//      notices that are present in the software.
// 
//      (E) If you distribute any portion of the software in source code form, you may do so only under this license by 
//      including a complete copy of this license with your distribution. If you distribute any portion of the software in 
//      compiled or object code form, you may only do so under a license that complies with this license.
// 
//      (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, 
//      guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot 
//      change. To the extent permitted under your local laws, the contributors exclude the implied warranties of 
//      merchantability, fitness for a particular purpose and non-infringement.
//
// </copyright>
// -----------------------------------------------------------------------

using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace System.Collections.Generic
{
    /// <summary>
    ///     An observable collection that groups results.
    /// </summary>
    public class GroupedObservableCollection<TGroup, TValue> : GroupedCollection<TGroup, TValue>, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<KeyValuePair<TGroup, ReadOnlyObservableCollection<TValue>>>, IEnumerable
    {
        public GroupedObservableCollection(GroupDecider<TGroup, TValue> decider) : base(decider)
        {
        }

        /// <summary>
        ///     Adds an item to the collection.
        /// </summary>
        /// <param name="value">
        ///     The item to add to the collection.
        /// </param>
        /// <returns>
        ///     The group that <paramref name="value"/> was put in.
        /// </returns>
        protected override TGroup AddItem(TValue item)
        {
            TGroup value = base.AddItem(item);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
            return value;
        }

        /// <summary>
        ///     Clears the collection.
        /// </summary>
        protected override void ClearItems()
        {
            base.ClearItems();
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        /// <summary>
        ///     Removes an item from the collection.
        /// </summary>
        /// <param name="value">
        ///     The item to remove.
        /// </param>
        /// <returns>
        ///     If <paramref name="value"/> was removed returns <c>true</c>, otherwise <c>false</c>.
        /// </returns>
        protected override bool RemoveItem(TValue item)
        {
            bool value = base.RemoveItem(item);
            if (value)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
            }
            return value;
        }

        /// <summary>
        ///     Called when the collection changes.
        /// </summary>
        /// <param name="e">
        ///     A <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> that describes the changes.
        /// </param>
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged.Invoke(this, e);
            }
        }

        /// <summary>
        ///     Creates a new collection to use in a group.
        /// </summary>
        /// <returns>
        ///     A new observable collection of <typeparamref name="TValue"/>
        /// </returns>
        /// <remarks>
        ///     Allows inheritors to have a different type of collection used, as long as it inherits from <see cref="System.Collections.ObjectModel.Collection{T}"/>.
        /// </remarks>
        protected override ObjectModel.Collection<TValue> CreateNewCollection()
        {
            return new ObservableCollection<TValue>();
        }

        /// <summary>
        ///     The items in a group.
        /// </summary>
        /// <param name="group">
        ///     The group to get items for.
        /// </param>
        /// <returns>
        ///     A observable collection of items in the group <paramref name="group"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        ///     <paramref name="group"/> cannot be <c>null</c> (<c>Nothing</c> in Visual Basic).
        /// </exception>
        /// <remarks>
        ///     If the group does not exist it is created.
        /// </remarks>
        public new ReadOnlyObservableCollection<TValue> this[TGroup group]
        {
            get
            {
                if (group == null)
                {
                    throw new ArgumentNullException("group", "'group' cannot be null (Nothing in Visual Basic).");
                }

                if (!Collection.ContainsKey(group))
                {
                    Collection.Add(group, CreateNewCollection());
                }
                return new ReadOnlyObservableCollection<TValue>((ObservableCollection<TValue>)Collection[group]);
            }
        }

        #region INotifyCollectionChanged Members

        /// <summary>
        ///     Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        ///     Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region IEnumerable<KeyValuePair<TGroup,ReadOnlyObservableCollection<TValue>>> Members

        public new IEnumerator<KeyValuePair<TGroup, ReadOnlyObservableCollection<TValue>>> GetEnumerator()
        {
            foreach (TGroup key in Collection.Keys)
            {
                yield return new KeyValuePair<TGroup, ReadOnlyObservableCollection<TValue>>(key, new ReadOnlyObservableCollection<TValue>((ObservableCollection<TValue>)Collection[key]));
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (TGroup key in Collection.Keys)
            {
                yield return new KeyValuePair<TGroup, ReadOnlyObservableCollection<TValue>>(key, new ReadOnlyObservableCollection<TValue>((ObservableCollection<TValue>)Collection[key]));
            }
        }

        #endregion
    }
}
